﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OAuth;
using System.Net;
using System.IO;
using NObsidian.Managers;
using System.Runtime.Serialization.Json;

namespace NObsidian
{
    public class ObsidianProxy
    {
        public const string DEFAULT_REQUEST_TOKEN_URI = "https://www.obsidianportal.com/oauth/request_token";
        public const string DEFAULT_AUTHORIZE_URI = "https://www.obsidianportal.com/oauth/authorize";
        public const string DEFAULT_ACCESS_TOKEN_URI = "https://www.obsidianportal.com/oauth/access_token";

        private string _consumerKey;
        private string _consumerSecret;
        private string _token;
        private string _tokenSecret;

        private OAuthBase _ouath;


        public string RequestTokenURI { get; set; }
        public string AuthorizeURI { get; set; }
        public string AccessTokenURI { get; set; }

        public CampaignManager CampaignManager { get; set; }
        public UserManager UserManager { get; set; }
        public CharacterManager CharacterManager { get; set; }
        public WikiPageManager WikiPageManager { get; set; }
        
        public ObsidianProxy(string consumerKey, string consumerSecret)
        {
            _consumerKey = consumerKey;
            _consumerSecret = consumerSecret;

            RequestTokenURI = DEFAULT_REQUEST_TOKEN_URI;
            AuthorizeURI = DEFAULT_AUTHORIZE_URI;
            AccessTokenURI = DEFAULT_ACCESS_TOKEN_URI;

            CampaignManager = new Managers.CampaignManager(this);
            CharacterManager = new Managers.CharacterManager(this);
            UserManager = new Managers.UserManager(this);
            WikiPageManager = new Managers.WikiPageManager(this);

            _ouath = new OAuthBase();
        }

        #region Authentication

        public string FirstLoginStep()
        {
            HttpWebRequest request;
            WebResponse response;
            Uri uri;
            string outAddress;
            string outParams;
            string signature;
            string timeStamp;
            string nonce;
            string postHeader;

            uri = new Uri(RequestTokenURI);
            timeStamp = _ouath.GenerateTimeStamp();
            nonce = _ouath.GenerateNonce();

            // TODO remove unused paramters and return a proper signature straight away
            signature = _ouath.GenerateSignature(uri, _consumerKey, _consumerSecret, null, null, "POST",
                timeStamp, nonce, OAuthBase.SignatureTypes.HMACSHA1, out outAddress, out outParams);
            signature = _ouath.UrlEncode(signature);

            postHeader = "OAuth oauth_nonce={0}, oauth_signature_method={1}, oauth_timestamp={2}, oauth_consumer_key={3}, oauth_signature={4}, oauth_version=1.0";
            postHeader = string.Format(postHeader, nonce, "HMAC-SHA1", timeStamp, _consumerKey, signature);
                        
            request = WebRequest.Create(outAddress) as HttpWebRequest;
            request.Method = "POST";
            request.Headers.Add("Authorization:" + postHeader);
            response = request.GetResponse();

            return AuthorizeURI + "?" + GetTokenParameter(response);
        }

        public void SecondLoginStep(string pin)
        {
            HttpWebRequest request;
            WebResponse response;
            string timeStamp;
            string nonce;
            string outAddress;
            string outParams;
            string signature;
            string postHeader;
            Uri uri;

            _ouath = new OAuthBase();

            timeStamp = _ouath.GenerateTimeStamp();
            nonce = _ouath.GenerateNonce();

            uri = new Uri(AccessTokenURI);
            signature = _ouath.GenerateSignature(uri, _consumerKey, _consumerSecret, _token, _tokenSecret,
                "POST", timeStamp, nonce, out outAddress, out outParams);
            signature = _ouath.UrlEncode(signature);

            postHeader = "OAuth oauth_nonce={0}, oauth_signature_method={1}, oauth_timestamp={2}, oauth_consumer_key={3}, oauth_token={5}, oauth_verifier={6}, oauth_signature={4}, oauth_version=1.0";
            postHeader = string.Format(postHeader, nonce, "HMAC-SHA1", timeStamp, _consumerKey, signature, _token, pin);
            request = WebRequest.Create(outAddress) as HttpWebRequest;
            request.Method = "POST";
            request.Headers.Add("Authorization:" + postHeader);

            
            response = request.GetResponse();
            GetTokenParameter(response);
        }

        private string GetTokenParameter(WebResponse response)
        {
            string parameter;
            string parameters;
            int start;
            int len;

            parameters = GetResponseText(response);

            start = parameters.IndexOf("oauth_token_secret=");
            len = parameters.Substring(start).IndexOf('&');
            if (len <= 0)
            {
                parameter = parameters.Substring(start);
            }
            else
            {
                parameter = parameters.Substring(start, len);
            }
            _tokenSecret = parameter.Substring(parameter.IndexOf('=') + 1);

            start = parameters.IndexOf("oauth_token=");
            len = parameters.Substring(start).IndexOf('&');
            parameter = parameters.Substring(start, len);
            _token = parameter.Substring(parameter.IndexOf('=') + 1);

            return parameter;
        }
        #endregion

        public string GetObsidianData(string url)
        {
            HttpWebRequest request;
            WebResponse response;
            string timeStamp;
            string nonce;
            string outAddress;
            string outParams;
            string signature;
            string postHeader;
            OAuthBase oauth;
            Uri uri;

            try
            {
                oauth = new OAuthBase();

                timeStamp = oauth.GenerateTimeStamp();
                nonce = oauth.GenerateNonce();

                uri = new Uri(url);
                signature = oauth.GenerateSignature(uri, _consumerKey, _consumerSecret, _token, _tokenSecret,
                    "GET", timeStamp, nonce, out outAddress, out outParams);
                signature = oauth.UrlEncode(signature);

                postHeader = "OAuth oauth_nonce={0}, oauth_signature_method={1}, oauth_timestamp={2}, oauth_consumer_key={3}, oauth_token={5},  oauth_signature={4}, oauth_version=1.0";
                postHeader = string.Format(postHeader, nonce, "HMAC-SHA1", timeStamp, _consumerKey, signature, _token);
                request = WebRequest.Create(outAddress) as HttpWebRequest;
                request.Method = "GET";
                request.Headers.Add("Authorization:" + postHeader);

                response = request.GetResponse();
            }            
            catch (WebException ex)
            {
                throw new Exception(ex.Message + "\n" + GetResponseText(ex.Response));
            }

            return GetResponseText(response);
        }

        public string UpdateObsidianData(string url, string data)
        {
            HttpWebRequest request;
            WebResponse response;
            string timeStamp;
            string nonce;
            string outAddress;
            string outParams;
            string signature;
            string postHeader;
            OAuthBase oauth;
            Uri uri;

            try
            {
                oauth = new OAuthBase();

                timeStamp = oauth.GenerateTimeStamp();
                nonce = oauth.GenerateNonce();

                uri = new Uri(url);
                signature = oauth.GenerateSignature(uri, _consumerKey, _consumerSecret, _token, _tokenSecret,
                    "PUT", timeStamp, nonce, out outAddress, out outParams);
                signature = oauth.UrlEncode(signature);

                postHeader = "OAuth oauth_nonce={0}, oauth_signature_method={1}, oauth_timestamp={2}, oauth_consumer_key={3}, oauth_token={5},  oauth_signature={4}, oauth_version=1.0";
                postHeader = string.Format(postHeader, nonce, "HMAC-SHA1", timeStamp, _consumerKey, signature, _token);
                request = WebRequest.Create(outAddress) as HttpWebRequest;
                request.Method = "PUT";
                request.Headers.Add("Authorization:" + postHeader);

                byte[] bytes = Encoding.UTF8.GetBytes(data);
                request.ContentLength = bytes.Length;
                Stream s = request.GetRequestStream();
                s.Write(bytes, 0, bytes.Length);

                response = request.GetResponse();
            }
            catch (WebException ex)
            {
                throw new Exception(GetResponseText(ex.Response));
            }

            return GetResponseText(response);
        }

        public string CreateObsidianData(string url, string data)
        {
            HttpWebRequest request;
            WebResponse response;
            string timeStamp;
            string nonce;
            string outAddress;
            string outParams;
            string signature;
            string postHeader;
            OAuthBase oauth;
            Uri uri;

            try
            {
                oauth = new OAuthBase();

                timeStamp = oauth.GenerateTimeStamp();
                nonce = oauth.GenerateNonce();

                uri = new Uri(url);
                signature = oauth.GenerateSignature(uri, _consumerKey, _consumerSecret, _token, _tokenSecret,
                    "POST", timeStamp, nonce, out outAddress, out outParams);
                signature = oauth.UrlEncode(signature);

                postHeader = "OAuth oauth_nonce={0}, oauth_signature_method={1}, oauth_timestamp={2}, oauth_consumer_key={3}, oauth_token={5},  oauth_signature={4}, oauth_version=1.0";
                postHeader = string.Format(postHeader, nonce, "HMAC-SHA1", timeStamp, _consumerKey, signature, _token);
                request = WebRequest.Create(outAddress) as HttpWebRequest;
                request.Method = "POST";
                request.Headers.Add("Authorization:" + postHeader);

                byte[] bytes = Encoding.UTF8.GetBytes(data);
                request.ContentLength = bytes.Length;
                Stream s = request.GetRequestStream();
                s.Write(bytes, 0, bytes.Length);

                response = request.GetResponse();
            }
            catch (WebException ex)
            {
                throw new Exception(GetResponseText(ex.Response));
            }
            return GetResponseText(response);
        }

        public string DeleteObsidianData(string url)
        {
            HttpWebRequest request;
            WebResponse response;
            string timeStamp;
            string nonce;
            string outAddress;
            string outParams;
            string signature;
            string postHeader;
            OAuthBase oauth;
            Uri uri;

            try
            {
                oauth = new OAuthBase();

                timeStamp = oauth.GenerateTimeStamp();
                nonce = oauth.GenerateNonce();

                uri = new Uri(url);
                signature = oauth.GenerateSignature(uri, _consumerKey, _consumerSecret, _token, _tokenSecret,
                    "DELETE", timeStamp, nonce, out outAddress, out outParams);
                signature = oauth.UrlEncode(signature);

                postHeader = "OAuth oauth_nonce={0}, oauth_signature_method={1}, oauth_timestamp={2}, oauth_consumer_key={3}, oauth_token={5},  oauth_signature={4}, oauth_version=1.0";
                postHeader = string.Format(postHeader, nonce, "HMAC-SHA1", timeStamp, _consumerKey, signature, _token);
                request = WebRequest.Create(outAddress) as HttpWebRequest;
                request.Method = "DELETE";
                request.Headers.Add("Authorization:" + postHeader);

                response = request.GetResponse();
            }
            catch (WebException ex)
            {
                throw new Exception(GetResponseText(ex.Response));
            }

            return GetResponseText(response);
        }

        public T GetObjectFromJson<T>(string json)
        {
            DataContractJsonSerializer ser;
            T obj;

            ser = new DataContractJsonSerializer(typeof(T));
            Stream s = new MemoryStream(UTF8Encoding.Default.GetBytes(json));

            obj = (T) ser.ReadObject(s);

            if (obj is Containers.ContainerBase)
            {
                (obj as Containers.ContainerBase).PopulateOriginalValues();
            }

            return obj;
        }

        public string GetJSONFromObject<T>(T obj)
        {
            DataContractJsonSerializer ser;
            string json;

            ser = new DataContractJsonSerializer(typeof(T));
            MemoryStream ms = new MemoryStream();
            ser.WriteObject(ms, obj);

            json = Encoding.Default.GetString(ms.ToArray());

            /*
             * Updated to default value properties need to be obtained too.
             * An suggested approach is to:
             *  - Get the list of properties changed (easy as we have the original values)
             *  - If any of those properties have the default value manually insert in t
             *    he json string the appropiate key/value pair
             */

            return json;
        }

        
        private static string GetResponseText(WebResponse response)
        {
            Stream receiveStream = response.GetResponseStream();
            Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
            StreamReader readStream = new StreamReader(receiveStream, encode);
            Char[] read = new Char[256];
            // Reads 256 characters at a time.    
            int count = readStream.Read(read, 0, 256);

            string s = string.Empty;

            while (count > 0)
            {
                // Dumps the 256 characters on a string and displays the string to the console.
                String str = new String(read, 0, count);
                s += str;
                count = readStream.Read(read, 0, 256);
            }
            Console.WriteLine("");
            // Releases the resources of the response.
            response.Close();
            // Releases the resources of the Stream.
            readStream.Close();

            return s;
        }

    }
}
